-module(wish_lib).

-compile(export_all).

-include("wish_lib.hrl").

-import(lists, [foreach/2, map/2, max/1, reverse/1]).

-export([runit/1, start/0, cast/1]).

start() ->
    run(0),
    run(1),
    run(2).

run(N) ->
    spawn(fun() -> runit(N) end).
		  

%% Drawing list
%%   {draw, [{X,Y,W,H,O}]}
%%   at X Y draw an object of size W, H called O

runit(N) ->
    L = layout(N),
    io:format("L=~p~n",[L]),
    render("layout" ++ integer_to_list(N), style, L).

layout(0) ->
    {draw, [{10, 10, 80, 20, {button, "Press Me"}},
	    {10, 40, 60, 20, {entry,"Hello"}},
	    {10, 70, 60, 20, {combo, names()}},
	    {10, 110, 60, 20, {label, "I am a label"}},
	    {200, 10, 120, 100, {listBox, names()}},
	    {200, 150, 120, 100, {editor, "Hello"}},
	    {10, 300, 100, 100, 
	     {labelFrame, "Label", 
	      {draw, [{10,10,60,15,{button, "Click"}}]}}}
	   ]};
layout(1) ->
    H = hbox(5, [{obj, 60,20, mkLabel("one")},
		 {obj, 80,20, mkEntry("two")}]),
    V = vbox(0, [H,H]),
    L = labelFrame("Stuff", V),
    B = hbox(5, [{obj, 60, 20, mkButton("one")},
		 {obj, 60, 20, mkButton("two")},
		 {obj, 60, 20, mkButton("three")},
		 {obj, 100, 20, mkCombo(names())}]),
    N = {obj, 100, 100, mkListBox(names())},
    E = {obj, 100, 100, mkEditor("joe")},
    Check = {obj, 60, 20, mkCheckButton("Hello")},
    Radio = {obj, 60, 90, mkRadio("radio", ["this","that","other"])},

    H1 = hbox(5, [N,E,Check]),
    Pwd = {obj, 100, 20, password},
    Lr = labelFrame("Password", Pwd),
    vbox(2, [L, labelFrame("Buttons", B), H1, hbox(5, [Radio, Lr])]);
layout(2) ->
    Pwd = {obj, 100, 20, password},
    Lr = labelFrame("Password", Pwd).

%% Objects are either simple or complex
%%  @type obj() = prim() | complex()
%%  @type prim() = {prim, W, H, O} 
%%      is a primitive object with no internal structure
%%  @type complex() = {complex, W, H, Args, obj()}
%%      a layout tells where objects are postitioned within their
%%      parent - objects vcan be comple or complex
%%  
%%  @type layout() = {layout, W, H, [{X,Y,obj()}]}

labelFrame(Str, L) ->
    {W,H} = bbox(L),
    {draw, [{5,5, W + 20, H+20, {labelFrame, Str, L}}]}.

names() ->
    ["one","two","three",
     "four","five","six","seven","eight","none","ten",
     "one","two","three","four","five","six","seven","eight","none","ten",
     "one","two","three","four","five","six","seven","eight","none","ten"].

mkButton(Str) -> {button, Str}.

mkEntry(Str) -> {entry, Str}.
    
mkLabel(Str) ->{label, Str}.
    
mkCombo(L) -> {combo, L}.
    
mkListBox(L) -> {listBox, L}.

mkRadio(Str, L) -> {radio, Str, L}.
    
mkEditor(L) -> {editor, L}.

mkCheckButton(C) -> {checkButton, C}.

render(Title, _Defaults, L) ->
    wish:start(),
    {W, H} = S = bbox(L),
    Border=10,
    io:format("S=~p~n",[S]),
    mkWin(#win{y=200,title=Title,name=test,w=W+2*Border,h=H+2*Border},
	  fun({win,Win}, _State) -> 
		  %% io:format("here:~p ~p~n",[Win, L]),
		  %% Make a regular button
		  Can = [Win,".c"],
		  Bg = ?bg,
		  cast([canvas,Can,{bg, Bg},{highlightthickness,0}]),
		  cast([pack,Can,{fill,both},{expand,true}]),
		  render_things(Can, 10, 10, L),
		  Can
	  end,
	  fun(Any, S1) ->
		  io:format("Handle1:~p~n",[Any]),
		  S1
	  end).

path2name([I]) -> [i2s(I)];
path2name([H|T]) -> [i2s(H),"."|path2name(T)].

i2s(I) -> integer_to_list(I).

render_things(Can, X, Y, {draw, L}) ->
    foreach(fun(O) ->  render_obj(Can, X, Y, O) end, L);
render_things(Can, X, Y, {obj, W,H,O}) ->
    render_obj(Can, X, Y, {0, 0, W, H, O}).

render_obj(Can, X, Y, {X1, Y1, W, H, {button,Str}}) ->
    makeButton(Can, X+X1, Y+Y1,  W,  H, Str);
render_obj(Can, X, Y, {X1, Y1, W, H, {checkButton,Str}}) ->
    makeCheckButton(Can, X+X1, Y+Y1,  W,  H, Str);
render_obj(Can, X, Y, {X1, Y1, W, H, {combo,L}}) ->
    makeCombo(Can, X+X1, Y+Y1, W, H, L);
render_obj(Can, X, Y, {X1, Y1, W, H, {entry,Str}}) ->
    makeEntry(Can, X+X1, Y+Y1, W, H, Str);
render_obj(Can, X, Y, {X1,Y1, _, _, {label,Str}}) ->
    Tag = [Can,".",i2s(wish:new_index())],
    cast([Can,create,text,X+X1,Y+Y1,{anchor, nw},{tags,Tag},{fill,"red"},
	  {text,Str}]);
render_obj(Can, X, Y, {X1, Y1,  W, H, {listBox,L}}) ->
    makeListBox(Can, X+X1, Y+Y1, W , H, L);
render_obj(Can, X, Y,  {X1, Y1, W, H, {editor,Str}}) ->
    makeEditor(Can, X+X1, Y+Y1, W, H, Str);
render_obj(Can, X, Y,  {X1, Y1, W, H, {labelFrame,Str,L }}) ->
    LabelFrame = makeCanvasWindow(Can, X+X1, Y+Y1, W, H),
    cast([labelframe,LabelFrame,{text,Str},{bg,?bg},{relief,ridge}]),
    cast([place,LabelFrame,{relwidth,"1.0"}, {relheight,"1.0"}]),
    Canvas = [LabelFrame,".c"],
    cast([canvas,Canvas,{bg,?bg},{bd,0},{highlightthickness,0}]),
    cast([pack, Canvas]),
    render_things(Canvas, 5, 0, L);
render_obj(Can, X, Y, {X1, Y1,  W, H, {radio,Str, L}}) ->
    makeRadio(Can, X+X1, Y+Y1, W , H, Str, L);
render_obj(Can, X, Y,  {X1, Y1, _W, _H, D={draw, _}}) ->
    render_things(Can, X+X1, Y+Y1, D);
render_obj(Can, X, Y, {X1, Y1, W, H, password}) ->
    makePassword(Can, X+X1, Y+Y1, W, H);

render_obj(_Can, X, Y, F) ->
    io:format("** render_obj:~p ~p ~p~n",[X, Y, F]).

makeCanvasWindow(Can, X, Y, W, H) ->
    Frame = [Can,".", i2s(wish:new_index())],
    cast([frame, Frame, {width,W},{height,H}]),
    cast([Can,create,window,X,Y,{anchor,nw},{window,Frame}]),
    [Frame,".x"].

makeButton(Can, X, Y, W, H, Str) ->
    Button = makeCanvasWindow(Can, X, Y, W, H),
    cast([button,Button, {text,Str},{command, sendSock(Button, Str)}]),
    cast([place,Button,{relwidth,"1.0"}, {relheight,"1.0"}]).

makeCheckButton(Can, X, Y, W, H, Str) ->
    Button = makeCanvasWindow(Can, X, Y, W, H),
    Index = wish:new_index(),
    Var = "vv(" ++ i2s(Index) ++ ")",
    io:format("GC me ...~n"),
    cast([checkbutton,Button, {text,Str},{bg,?bg},{variable,Var},
	  {command, sendCheckButton(Button, i2s(Index))}]),
    cast([place,Button,{relwidth,"1.0"}, {relheight,"1.0"}]).

makeCombo(Can, X, Y, W, H, L) ->
    Button = makeCanvasWindow(Can, X, Y, W, H),
    cast(["iwidgets::combobox", Button,
	  {selectioncommand, sendCombo(Button)}]),
    cast([Button,insert,list,'end',{strings,L}]),
    cast([Button,selection,set,hd(L)]),
    cast([place,Button,{relwidth,"1.0"}, {relheight,"1.0"}]).

makeEntry(Can, X, Y, W, H, Str) ->
    Entry = makeCanvasWindow(Can, X, Y, W, H),
    cast([entry,Entry]),
    cast([place,Entry,{relwidth,"1.0"}, {relheight,"1.0"}]),
    cast([bind,Entry,"<Return>", "readEntry " ++ Entry]),
    cast([Entry,insert,0,Str]).

makeEditor(Can, X, Y, W, H, Str) ->
    Frame = [Can,".",i2s(wish:new_index())],
    cast([frame, Frame, {width,W},{height,H}]),
    cast([Can,create,window,X,Y,{anchor,nw},{window,Frame}]),
    Frame1 = [Frame,".x"],
    cast([frame, Frame1]),
    Scroll = [Frame1,".b"],
    Text = [Frame1,".l"],
    cast([text,Text]),
    cast([scrollbar,Scroll]),
    cast([Scroll, config, {command, Text ++ " yview"}]),
    cast([Text, config, {yscrollcommand, Scroll ++ " set"}]),
    cast([pack,Scroll,{side,right},{fill,y}]),
    cast([pack,Text,{fill,both},{expand,1}]),
    cast([place,Frame1,{relwidth,"1.0"},{relheight,"1.0"}]),
    cast([Text,insert,"0.0",Str]).

makeListBox(Can, X, Y, W , H, L) ->
    Frame = [Can,".", i2s(wish:new_index())],
    cast([frame, Frame, {width,W},{height,H}]),
    cast([Can,create,window,X,Y,{anchor,nw},{window,Frame}]),
    Frame1 = [Frame,".x"],
    cast([frame, Frame1]),
    Scroll = [Frame1,".b"],
    List = [Frame1,".l"],
    cast([scrollbar,Scroll, {command, List ++ " yview"}]),
    cast([listbox,List,{yscroll, Scroll ++ " set"}]),
    cast([pack,Scroll,{side,right}, {fill,y},{expand,1}]),
    cast([pack,List,{fill,both},{expand,1}]),
    cast([place,Frame1,{relwidth,"1.0"},{relheight,"1.0"}]),
    cast([List,insert,0,{strings,L}]),
    cast([bind,List,"<Double-1>",get_selection(List)]),
    true.

makePassword(Can, X, Y, W, H) ->
    Entry = makeCanvasWindow(Can, X, Y, W, H),
    cast([entry,Entry,{validate,key},{show,"*"}]),
    cast([place,Entry,{relwidth,"1.0"}, {relheight,"1.0"}]),
    cast([bind,Entry,"<Return>", "readEntry " ++ Entry]).

makeRadio(Can, X, Y, W, H, Name, L) ->
    LabelFrame = makeCanvasWindow(Can, X, Y, W, H),
    cast([labelframe,LabelFrame,{text,Name},{bg,?bg},{relief,ridge}]),
    cast([place,LabelFrame,{relwidth,"1.0"}, {relheight,"1.0"}]),
    Idx  =i2s(wish:new_index()),
    Var = "vv(" ++ Idx ++ ")",
    foreach(fun(Str) ->
		    I = i2s(wish:new_index()),
		    R = [LabelFrame,".",I],
		    cast([radiobutton, R, {text,Str},{bg,?bg},
			  {command, sendRadioButton(LabelFrame, Idx)},
			  {variable,Var},{value,Str}, {relief,flat}]),
		    cast([pack, R,{side,top},{pady,2},{anchor,w}])
	    end, L).

%% nameOf(X) when atom(X) ->
%%     atom_to_list(X);
%% nameOf(T) when tuple(T) ->
%%     nameOf(element(1,T)).

bbox({draw, L}) ->
    Width = max(map(fun({X,_Y, W,_H, _}) -> X +  W end, L)),
    Ht  = max(map(fun({_X,Y, _W,H, _}) -> Y +  H end, L)),
    {Width, Ht};
bbox(X) ->
    {element(2,X), element(3, X)}.

%% Hbox constructor

%%    hbox(5, [{obj,60,20,mkButton("one")},
%%	       {obj,80,20,mkButton("two")}] =>
%%    {draw, [{5,5,60,20, O}, {70,20,80,20,O1}]


hbox(Delta, L) -> 
    hbox(Delta, Delta, Delta, L, 0, []).
    
hbox(X, Y, Delta, [D={draw,_}|T], MaxWidth, L) ->
    {W,H} = bbox(D),
    MaxWidth1 = max([MaxWidth, H]),
    hbox(X + W + Delta, Y, Delta, T, MaxWidth1, [{X,Y,W,H,D}|L]);
hbox(X, Y, Delta, [{obj,W,H,O}|T], MaxHt, L) ->
    MaxHt1 = max([MaxHt, H]),
    hbox(X + W + Delta, Y, Delta, T, MaxHt1, [{X,Y,W,H,O}|L]);
hbox(_X, _, _Delta, [], _MaxHt, L) ->
    {draw, L}.

vbox(Delta, L) -> 
    vbox(Delta, Delta, Delta, L, 0, []).
    
vbox(X, Y, Delta, [D={draw,_}|T], MaxWidth, L) ->
    {W,H} = bbox(D),
    MaxWidth1 = max([MaxWidth, W]),
    vbox(X, Y + H + Delta, Delta, T, MaxWidth1, [{X,Y,W,H,D}|L]);
vbox(X, Y, Delta, [{obj,W,H,O}|T], MaxWidth, L) ->
    MaxWidth1 = max([MaxWidth, W]),
    vbox(X, Y + H + Delta, Delta, T, MaxWidth1, [{X,Y,W,H,O}|L]);
vbox(_X, Y, Delta, [], MaxWidth, L) ->
    {draw, L}.

get_selection(List) ->
    ["getListSelection {",List,"}"].

sendCheckButton(W, Var) ->
    ["sendCheckButton ", W," ", Var].

sendRadioButton(W, Var) ->
    ["sendRadioButton ", W," ", Var].

sendSock(W, Str) ->
    ["sendSocket {",W," ",Str,"}"].



sendCombo(W) ->
    ["sendCombo {",W," ","}"].

mkWin(Win, F1, F2) ->
    spawn(fun() -> start_win(Win, F1, F2) end).

start_win(Win, F1, F2) ->
    Id = mkWin(Win),
    State = F1(Id, Win),
    win_loop(State, F2).

win_loop(State, F2) ->
    receive
	Any ->
	    io:format("Window got:~p~n",[Any]),
	    State1 = F2(Any, State),
	    case State1 of
		stop ->
		    exit(normal);
		_ ->
		    win_loop(State1, F2)
	    end
    end.
							   
cast(L) ->    
    L1 = mk_cmd(L),
    %% L2 = lists:flatten(L1), io:format("L2=~s~n",[L2]),
    wish:cast(L1).
    
call(L) ->
    L1 = mk_cmd(L),
    %% io:format("L1=~p~n",[L1]),
    %% L2= L2 = lists:flatten(L1), io:format("~s~n",[L2]),
    wish:cmd(L1).

mk_cmd([{pass, S}|T]) ->
    [S|mk_cmd(T)];
mk_cmd([{strings, L}|T]) ->
    L1 = map(fun(I) -> [to_ascii(I)," "] end, L),
    L1 ++ mk_cmd(T);
mk_cmd([{tags, L}|T]) ->
    ["-tag {", interlieve(L, $\s), "} "|mk_cmd(T)];
mk_cmd([{Tag,Val}|T]) ->
    ["-",atom_to_list(Tag)," ",to_ascii(Val)," "|mk_cmd(T)];
mk_cmd([H|T]) ->
    [to_ascii(H)," "|mk_cmd(T)];
mk_cmd([]) -> [].

%% to_asci was cut and paste from gstk.erl
%% 
%% ----- Value to String -----

to_ascii(V) when list(V)    -> [$",to_ascii(V,[],[]),$"];  %% it's a string
to_ascii(V) when integer(V) -> integer_to_list(V);
to_ascii(V) when float(V)   -> float_to_list(V);
to_ascii(V) when atom(V)    -> to_ascii( atom_to_list(V));
to_ascii(V) when tuple(V)   -> to_ascii(lists:flatten(io_lib:format("~w",[V
])));
to_ascii(V) when pid(V)     -> pid_to_list(V).

% FIXME: Currently we accept newlines in strings and handle this at
% the Tcl side. Is this the best way or should we translate to "\n"
% here?

%% to_ascii([$[|R], Y, X) ->  to_ascii(R, Y, [$[, $\\ | X]);
%% to_ascii([$]|R], Y, X) ->  to_ascii(R, Y, [$], $\\ | X]);
to_ascii([${|R], Y, X) ->  to_ascii(R, Y, [${, $\\ | X]);
to_ascii([$}|R], Y, X) ->  to_ascii(R, Y, [$}, $\\ | X]);
to_ascii([$"|R], Y, X) ->  to_ascii(R, Y, [$", $\\ | X]);
to_ascii([$$|R], Y, X) ->  to_ascii(R, Y, [$$, $\\ | X]);
to_ascii([$\\|R], Y, X) ->  to_ascii(R, Y, [$\\, $\\ | X]);
to_ascii([C|R], Y, X) when list(C) -> to_ascii(C, [R|Y], X);
to_ascii([C|R], Y, X) -> to_ascii(R, Y, [C|X]);
to_ascii([], [Y1|Y], X) -> to_ascii(Y1, Y, X);
to_ascii([], [], X) -> lists:reverse(X).

interlieve([X], _) -> [X];
interlieve([], _)  -> [];
interlieve([H|T], Sep) -> [H,Sep|interlieve(T, Sep)].

mkWin(#win{name=Tag, x=X, y=Y, w=Width, h=Ht, bg=Bg, title=Title, 
	   resize=Resizable}) ->
    wish:start(),
    Win = "." ++ integer_to_list(wish:reserveWin(Tag)),
    io:format("Win=~p~n",[Win]),
    cast([toplevel,Win,{bg,Bg},{bd,1},{relief,raised}]),
    Desc = [i2s(Width),"x",i2s(Ht),"+",i2s(X),"+",i2s(Y)],
    cast([wm,geometry,Win,{pass, Desc}]),
    case Resizable of
	true  -> void;
	false -> cast([wm,resizable,Win,0,0])
    end,
    cast([wm,title,Win,Title]),
    cast([bind, Win,"<Destroy>","windowDestroyed " ++ Win]),
    {win, Win}.
